<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 14-6-6
 * Time: 上午7:27
 */
namespace ulue\core\utils;

use Debug,
    ReflectionClass;

/**
 *
 * Invoke::object();
 * Invoke::call();
 **/
class Invoker
{

    static public function createObject($config)
    {
        return self::object();
    }

    /**
     * 实例化一个(构造函数可以有参数)的对象
     * @example 使用方式1：Invoker::object(classname,arg1,arg2,arg3...);
     *     第一个参数是 类名
     *     后面的就是 类的构造函数参数(可选)
     * @example 使用方式2：Invoker::object(array config);
     *   使用数组配置相关class信息,设置更为灵活
     *  $config = [
     *      'class' => 'app\helpers\Demo', // 类名
     *      'params' => [ arg1, arg2, arg3 ...]// 类的构造函数参数(可选)
     *      'attrs' => [ // 类的属性设置(可选)
     *           attr1  => value1,
     *           attr2  => value2,
     *           attr3  => value3,
     *           ...
     *      ]
     *  ]
     * @param $config
     * @throws \DMissArgumentException
     * @throws \DNotFoundException
     * @return object
     */
    static public function object($config)
    {
        $config    = self::_resolveArguments(func_get_args());

        $classname = $config['class'];

        if (!class_exists($classname)) {
            throw new \DNotFoundException('Required Class: '.$classname .' 没有找到!');
        }

        Debug::trace("i 准备从类[ {$classname} ]实例化一个对象 ",[
            '@internal \$classname' => $classname,
            '@internal \$config'    => $config
            ]);

        # code...
        if ( empty($config['params']) ) {
            $object  = new $classname;
        } else {
            $param  = $config['params'];
            $n      = count($param);

            if ($n===1) {
                $object = new $classname($param[0]);
            } else if ($n===2) {
                $object = new $classname($param[0],$param[1]);
            } else if ($n===3) {
                $object = new $classname($param[0],$param[1],$param[2]);
            } else {
                $class  = new \ReflectionClass($classname);
                $object = $class->newInstanceArgs($param);
            }
        }

        Debug::trace("成功实例化了 {$classname} ，返回对象");

        if ( !empty($config['attrs']) ) {
            foreach($config['attrs'] as $attr=>$value) {
                $object->$attr = $value;
            }
        }

        return $object;
    }

    static public function createCallback($config)
    {
        return self::callback();
    }

    /**
     * 类似于 class::object(),
     * 但不会立即实例化对象
     * 而是返回对象的可用回调 $callback
     *
     * 在外部可用 (最好在使用前 检测 is_callable($callback) )
     *     call_user_func($callback, $arg1,$arg2,...);
     * 来调用它;
     *
     * @param $config
     *
     * @example 使用方式2：Invoker::callback(array config);
     *   使用数组配置相关class信息,设置更为灵活
     *  $config = [
     *      'target' => 'app\helpers\Demo', // 目标，通常是一个类名 OR 'app\helpers\Demo::test' OR 'app\helpers\Demo->test'
     *      'params' => [ arg1, arg2, arg3 ...]// 类的构造函数参数(可选)
     *      'attrs' => [ // 类的属性设置(可选)
     *           attr1  => value1,
     *           attr2  => value2,
     *           attr3  => value3,
     *           ...
     *      ]
     *  ]
     *
     * @example 使用方式1：Invoker::callback(target,arg1,arg2,arg3...);
     *     第一个参数是 目标，通常是一个类名 OR 'app\helpers\Demo::test' OR 'app\helpers\Demo->test'
     *     后面的就是 类的构造函数参数(可选)
     * @throws \DMissArgumentException
     * @throws \DNotFoundException
     * @return \Closure $callback
     */
    static public function callback($config)
    {
        $config = self::_resolveArguments(func_get_args(), 'target');

        $target = $config['target'];

        Debug::trace("i 准备从[ {$target} ]创建一个可用回调(a Closure) ",[
            '@internal \$target' => $target,
            '@internal \$config'    => $config
            ]);

        $attrs  = !empty($config['attrs']) ? (array) $config['attrs'] : array();
        $params = !empty($config['params']) ? (array) $config['params'] : array();

        if ( ($pos=strpos($target,'::'))!==false ) {
            // $class  = substr($target, 0, $pos);
            // $method = substr($target, $pos+2);

            $callback     = function () use ($target, $params)
            {
                return !$params ? call_user_func($target) : call_user_func_array($target, $params);
            };
        } elseif ( ($pos=strpos($target,'->'))!==false ) {
            $class  = substr($target, 0, $pos);
            $method = substr($target, $pos+2);

            $callback     = function () use ($class, $method , $params)
            {
                $object = new $class;

                foreach($attrs as $attr=>$value) {
                    $object->$attr = $value;
                }

                return !$params ? $object->$method() : call_user_func_array([$object, $method ], $params);
            };
        } else {

            # only class name ...
            $classname = $target;

            if ( !$params ) {
                $callback  = function() use ($classname, $attrs)
                {
                    $object = new $classname;

                    foreach($attrs as $attr=>$value) {
                        $object->$attr = $value;
                    }

                    return $object;
                };
            } else {
                $callback  = function() use ($classname, $attrs, $params)
                {
                    $class  = new \ReflectionClass($classname);
                    $object = $class->newInstanceArgs($params);

                    foreach($attrs as $attr=>$value) {
                        $object->$attr = $value;
                    }

                    return $object;
                };
            }
        }

        return $callback;
    }

    /**
     * call 实例化对象同时执行对象中的方法
     * @param  mixed $callback 回调方法 | 也可以是回调相关信息,则会先调用self::callback() 创建回调
     * @param  array $args 回调方法参数，放入数组中
     * @throws \DRuntimeException
     * @throws \DNotFoundException
     * @throws \DUnknownCalledException
     * @internal param array|string $class 类相关配置 @see $this->object() : @param $config
     * @return mixed|object
     */
    static public function call($callback,array $args=[])
    {
        if ( !is_callable($callback) ) {
            $callback = $this->callback($callback);
        }

        if ( !is_callable($callback) ) {
            throw new \DUnknownCalledException('参数 $callback 不是一个可用的回调！');
        }

        try{
            # 有参数，调用回调并传入参数
            if ( !empty($args) ){
                $result = call_user_func_array($callback, $args  );

            # 无参数，调用回调
            } else {
                $result = call_user_func($callback);
            }
        }catch (\Exception $e){
            throw new \DRuntimeException("回调 方法调用失败了！",'call');
        }

        return $result;

    }

    /**
     * 解析参数
     * @param array $arguments
     * @throws \DMissArgumentException
     * @return array config
     */
    static protected function _resolveArguments(array $arguments, $keywords='class')
    {
        $n      = count($arguments);
        $config = array();

        if ( $n==1 ) {

            if (is_array($arguments[0])) {
                $config  =  $arguments[0];
            } else {
                $config  = array($keywords => $arguments[0]);
            }

        # 多个参数...
        } else if ( $n>1 ) {
            $config[$keywords] = array_shift($arguments);

            if ( !empty( $arguments )) {
                $config['params'] = $arguments;
            }
        }

        if (!isset($config[$keywords]) || !is_string($config[$keywords])) {
            // throw new \DMissArgumentException('Object configuration must be an array containing a "'.$keywords.'" element.');
            throw new \DMissArgumentException('对象的数组配置必须包含一个键为“'.$keywords.'”元素,键值为(string)类名。');
        }

        $config[$keywords] = str_replace([' ','.','/'], ['','\\','\\'], $config[$keywords]);

        return $config;
    }

    /**
     * 整理得到请求的类方法参数信息：
     * 根据 $class 类(对象)的映射，获取类方法($method)的参数信息，
     * 以此整理传入参数 $params 得到正确顺序的参数数组，
     * 便于在 调用方法时(call_user_fun_array) 按正确的顺序 映射传入的参数
     * @param string|object $class 类(对象)
     * @param  string $method $class类(对象)的方法
     * @param  array $params 传入的参数
     * @throws \DInvalidArgumentException
     * @return bool|array
     */
    static public function handleMethodParams($class, $method, array $params)
    {
        if ( !$params ) {
            return false;
        }

        // try {
        //     $reflection = new \ReflectionClass($class);
        // } catch (\ReflectionException $e) {
        //     throw new \DInstantiationException($e->getMessage());
        // }

        $reflectMethod = new \ReflectionMethod($class,$method);

        $parameters    = $reflectMethod->getParameters();
        $sortedParams  = [];

        foreach ($parameters as $p) {
            if ( isset($params[$p->name]) && $params[$p->name] !== '') {
                $sortedParams[$p->name] = $params[$p->name];
            } else if ( $p->isOptional() ) {
                $sortedParams[$p->name] = $p->getDefaultValue();
            } else {
                throw new \DInvalidArgumentException(Ulue::tl('{method}() of the {pos} param [${name}] is required, please pass in parameter values',[
                    '{method}' => $method,
                    '{pos}'    => $p->getPosition(),
                    '{name}'   => $p->name,
                    ])
                );
            }
        }

        return $sortedParams;
    }


}